package LiuYun

import spinal.core._
import spinal.lib._


class AddrTransReq(cat:Int) extends Bundle
{
  val va     = LISA.GPR  //vaddr
  val st     = ifGen(cat==LISA.TLB.DATA)(Bool())
}

class TlbCsr extends Bundle{
    val crmd   = new CsrCRMD
    val tlbidx = new CsrTLBIDX
    val asid   = new CsrASID
    val tlbehi = new CsrTLBEHI
    val estat  = new CsrESTAT
    val tlbelo = Vec(new CsrTLBELO, 2)
    val dmw    = Vec(new CsrDMW, 2)
    def toTlbEHi():TlbEHi = {
        val hi = new TlbEHi
        hi.e    := !tlbidx.ne || estat.ecode === LISA.Ex.TLBR.code
        hi.vppn := tlbehi.vppn
        hi.ps   := tlbidx.ps === LISA.ps_max
        hi.asid := asid.asid
        hi.g    := tlbelo.map(_.g).reduce(_&&_)
        hi
    }
    def collect(csr:Csr){
        crmd   := csr.crmd  
        estat  := csr.estat
        tlbidx := csr.tlbidx
        asid   := csr.asid  
        tlbehi := csr.tlbehi
        tlbelo(0) := csr.tlbelo0
        tlbelo(1) := csr.tlbelo1
        dmw(0) := csr.dmw0
        dmw(1) := csr.dmw1
    }
}

class AddrTransResp() extends Bundle
{
  val hit   = Bool()
  val pt   :UInt = UInt(LISA.w_ppn bits) //ptag
  val mat   = UInt(2 bits)
  val ex   = Bool()
  val ecode:UInt = LISA.Ex.Code
  val esubc:UInt = LISA.Ex.Subc
  def assignedFrom(pa:UInt,mat:UInt,hit:Bool):this.type = {
        this.hit := hit
        this.pt  := pa(pa.high downto pa.getWidth - LISA.w_ppn)
        this.mat := mat
        this.ex    := False
        this.ecode := U(0)
        this.esubc := U(0)
        this
}  
def cached() : Bool = mat === U(1)

def tagCompare(tagv:IndexedSeq[TagVBlockRam]):Bits = {
        val hits:IndexedSeq[Bool] = tagv.map(_.io.rd === U("1") @@ pt)
        val hit :Bool = hits.reduce(_||_)
        return (Vec(hits).asBits)
}
}

class AddrTrans(cat:Int) extends Component
{
  val io = new Bundle{
    val from = new Bundle{
      val csr = in(new TlbCsr)
      val request = slave(Stream(new AddrTransReq(cat)))
      val l2_tlb  = slave(new L2ToL1TLB())
    }
    val to = new Bundle{
      val response = out(new AddrTransResp())
    }
  }

  val csr = io.from.csr
  /** entities of L1TLB */
  val l1_tlb = (new L1TLB(cat))

  val s0 = new Bundle{
  //trans to L1 srch
    io.from.request.ready := True
    l1_tlb.io.from.srch.valid := io.from.request.valid
    l1_tlb.io.from.srch.vaddr := io.from.request.va
    if(cat==LISA.TLB.DATA){
      l1_tlb.io.from.srch.st := io.from.request.st
    }
    l1_tlb.io.from.l2_tlb.fillback   := io.from.l2_tlb.fillback
    l1_tlb.io.from.l2_tlb.invalidate := io.from.l2_tlb.invalidate
    l1_tlb.io.from.l2_tlb.entry      := io.from.l2_tlb.entry
    l1_tlb.io.from.l2_tlb.idx        := io.from.l2_tlb.idx

    l1_tlb.io.csr := csr
  }

    val PLRU = new Area{
    val idx = Bits(LISA.L1TLB.CAP bits) setAsReg() init(1)
    //FIXME: will use Pseudo Least Recent Use Algorithm
    idx := idx.rotateRight(1)

    io.from.l2_tlb.plru_idx := idx
  }

  val s1 = new Bundle{
    val valid = RegInit(False) 
    val vaddr = LISA.GPR setAsReg()

    valid := io.from.request.fire
    vaddr := io.from.request.va

    val addr_trans_resp = new AddrTransResp()

    val DMW_NUM     = 2

    val dmw_hit      = Bits(DMW_NUM bits)
    val dmw_hit_mat  = UInt(2       bits)
    val dmw_hit_pseg = UInt(3       bits)
    val dmw_mat      = Range(0, DMW_NUM).map(i=>csr.dmw(i).mat)
    val dmw_pseg      = Range(0, DMW_NUM).map(i=>csr.dmw(i).pseg)

    
    for(j <- 0 until DMW_NUM){
      dmw_hit(j) := csr.dmw(j).compare(vaddr, csr.crmd.plv)
    }
    dmw_hit_mat := MuxOH.or(dmw_hit, dmw_mat)
    dmw_hit_pseg := MuxOH.or(dmw_hit, dmw_pseg)
    

    
    when(io.from.csr.crmd.da && valid){
      addr_trans_resp.hit := True
      if(cat==LISA.TLB.FETCH){
        addr_trans_resp.mat := csr.crmd.datf
      }else{
        addr_trans_resp.mat := csr.crmd.datm
      }
      addr_trans_resp.pt  := vaddr(LISA.ps_min, LISA.TAG_WIDTH bits)

    }.elsewhen(dmw_hit.orR && valid){
      addr_trans_resp.hit   := True
      addr_trans_resp.mat   := dmw_hit_mat
      addr_trans_resp.pt    := dmw_hit_pseg@@vaddr(LISA.ps_min, LISA.TAG_WIDTH-3 bits)
    }.otherwise{
      addr_trans_resp.hit   := l1_tlb.io.to.resp.hit
      addr_trans_resp.mat   := l1_tlb.io.to.resp.mat
      addr_trans_resp.pt    := l1_tlb.io.to.resp.pt
    }

    when(csr.crmd.da || dmw_hit.orR){
      addr_trans_resp.ex := False
      addr_trans_resp.ecode := U(0)
      addr_trans_resp.esubc := U(0)
    }.otherwise{
      addr_trans_resp.ex    := l1_tlb.io.to.resp.ex
      addr_trans_resp.ecode := l1_tlb.io.to.resp.ecode
      addr_trans_resp.esubc := l1_tlb.io.to.resp.esubc
    }
    
    
    assert(
      assertion = io.from.csr.crmd.da ^ io.from.csr.crmd.pg,
      message   = "[Csr]: da & pg not set correctly!",
      severity  = FAILURE
    )
  }

  io.to.response := s1.addr_trans_resp
}